#include "stdafx.h"

#include <cctype>

#include "ResourceCache.h"

#include "ResourceLoaders\DefaultResourceLoader.h"

#include "..\Utilities\String.h"

//
// ResourceCache::ResourceCache							- Chapter 8, page 227
//
ResourceCache::ResourceCache(
	const unsigned int sizeInMb, 
	IResourceFile *resFile )
{
	_cacheSize = sizeInMb * 1024 * 1024;				// total memory size
	_allocated = 0;									// total memory allocated
	_pResourceFile = resFile;
}

//
// ResourceCache::~ResourceCache							- Chapter 8, page 227
//
ResourceCache::~ResourceCache()
{
	while (!_lru.empty())
	{
		FreeOneResource();
	}
	SAFE_DELETE(_pResourceFile);
}

//
// ResourceCache::Init								- Chapter 8, page 227
//
bool ResourceCache::Init()
{ 
	bool retValue = false;

	if ( _pResourceFile->Open() )
	{
		RegisterLoader(
			shared_ptr<IResourceLoader>(
				TWF_NEW DefaultResourceLoader()));

		retValue = true;
	}

	return retValue;
}

//
// ResourceCache::RegisterLoader						- Chapter 8, page 225
// 
//    The loaders are discussed on the page refereced above - this method simply adds the loader
//    to the resource cache.
//
void ResourceCache::RegisterLoader(shared_ptr<IResourceLoader> loader )
{
	_resourceLoaders.push_front(loader);
}

//
// ResourceCache::GetHandle							- Chapter 8, page 227
//
shared_ptr<ResourceHandle> ResourceCache::GetHandle(Resource * r)
{
	shared_ptr<ResourceHandle> handle(Find(r));

	if (handle==NULL)
	{
		handle = Load(r);
		TWF_ASSERT(handle);
	}
	else
	{
		Update(handle);
	}
	return handle;
}

//
// ResourceCache::Load								- Chapter 8, page 228-230
//
shared_ptr<ResourceHandle> ResourceCache::Load(Resource *r)
{
	// Create a new resource and add it to the lru list and map

	shared_ptr<IResourceLoader> loader;
	shared_ptr<ResourceHandle> handle;

	for (ResourceLoaders::iterator it = _resourceLoaders.begin(); 
		 it != _resourceLoaders.end(); 
		 ++it)
	{
		shared_ptr<IResourceLoader> testLoader = *it;

		if (WildcardMatch(testLoader->GetPattern().c_str(), r->Name.c_str()))
		{
			loader = testLoader;
			break;
		}
	}

	if (!loader)
	{
		TWF_ASSERT(loader && _T("Default resource loader not found!"));
		return handle;		// Resource not loaded!
	}

	int rawSize = _pResourceFile->GetRawResourceSize(*r);

	if (rawSize < 0)
	{
		TWF_ASSERT(rawSize > 0 && "Resource size returned -1 - Resource not found");
		return shared_ptr<ResourceHandle>();
	}

    int allocSize = rawSize + ((loader->AddNullZero()) 
		? (1) 
		: (0));

	char *rawBuffer = loader->UseRawFile() 
		? Allocate(allocSize) 
		: TWF_NEW char[allocSize];

    memset(rawBuffer, 0, allocSize);

	if ((rawBuffer == NULL) || 
		(_pResourceFile->GetRawResource(*r, rawBuffer) == 0))
	{
		// resource cache out of memory
		return shared_ptr<ResourceHandle>();
	}
	
	char *buffer = NULL;

	unsigned int size = 0;

	if (loader->UseRawFile())
	{
		buffer = rawBuffer;
		handle = shared_ptr<ResourceHandle>(
			TWF_NEW ResourceHandle(
				*r, 
				buffer, 
				rawSize, 
				this));
	}
	else
	{
		size = loader->GetLoadedResourceSize(rawBuffer, rawSize);

        buffer = Allocate(size);

		if (rawBuffer == NULL || buffer == NULL)
		{
			// resource cache out of memory
			return shared_ptr<ResourceHandle>();
		}

		handle = shared_ptr<ResourceHandle>(
			TWF_NEW ResourceHandle(
				*r, 
				buffer, 
				size, 
				this));

		bool success = loader->LoadResource(rawBuffer, rawSize, handle);
		
		// [mrmike] - This was added after the chapter went to copy edit. It is used for those
		//            resoruces that are converted to a useable format upon load, such as a compressed
		//            file. If the raw buffer from the resource file isn't needed, it shouldn't take up
		//            any additional memory, so we release it.
		//
		if (loader->DiscardRawBufferAfterLoad())
		{
			SAFE_DELETE_ARRAY(rawBuffer);
		}

		if (!success)
		{
			// resource cache out of memory
			return shared_ptr<ResourceHandle>();
		}
	}

	if (handle)
	{
		_lru.push_front(handle);
		_resources[r->Name] = handle;
	}

	TWF_ASSERT(loader && _T("Default resource loader not found!"));

	return handle;		// ResourceCache is out of memory!
}



//
// ResourceCache::Find									- Chapter 8, page 228
//
shared_ptr<ResourceHandle> ResourceCache::Find(Resource * r)
{
	ResourceHandleMap::iterator i = _resources.find(r->Name);
	
	if (i == _resources.end())
	{
		return shared_ptr<ResourceHandle>();
	}

	return i->second;
}

//
// ResourceCache::Update									- Chapter 8, page 228
//
void ResourceCache::Update(shared_ptr<ResourceHandle> handle)
{
	_lru.remove(handle);
	_lru.push_front(handle);
}

//
// ResourceCache::Allocate								- Chapter 8, page 230
//
char* ResourceCache::Allocate(unsigned int size)
{
	if (!MakeRoom(size))
		return NULL;

	char* mem = TWF_NEW char[size];

	if (mem)
	{
		_allocated += size;
	}

	return mem;
}


//
// ResourceCache::FreeOneResource						- Chapter 8, page 231
//
void ResourceCache::FreeOneResource()
{
	ResourceHandleList::iterator gonner = _lru.end();

	gonner--;

	shared_ptr<ResourceHandle> handle = *gonner;

	_lru.pop_back();

	_resources.erase(handle->_resource.Name);
	// Note - you can't change the resource cache size yet - the resource bits could still actually be
	// used by some sybsystem holding onto the ResHandle. Only when it goes out of scope can the memory
	// be actually free again.
}

//
// ResourceCache::Flush									- not described in the book
//
//    Frees every handle in the cache - this would be good to call if you are loading a new
//    level, or if you wanted to force a refresh of all the data in the cache - which might be 
//    good in a development environment.
//
void ResourceCache::Flush()
{
	while (!_lru.empty())
	{
		shared_ptr<ResourceHandle> handle = *(_lru.begin());

		Free(handle);

		_lru.pop_front();
	}
}

//
// ResourceCache::MakeRoom									- Chapter 8, page 231
//
bool ResourceCache::MakeRoom(unsigned int size)
{
	if (size > _cacheSize)
	{
		return false;
	}

	// return null if there's no possible way to allocate the memory
	while (size > (_cacheSize - _allocated))
	{
		// The cache is empty, and there's still not enough room.
		if (_lru.empty())

			return false;

		FreeOneResource();
	}

	return true;
}

//
//	ResourceCache::Free									- Chapter 8, page 228
//
void ResourceCache::Free(shared_ptr<ResourceHandle> gonner)
{
	_lru.remove(gonner);
	_resources.erase(gonner->_resource.Name);
	// Note - the resource might still be in use by something,
	// so the cache can't actually count the memory freed until the
	// ResHandle pointing to it is destroyed.

	//m_allocated -= gonner->m_resource.m_size;
	//delete gonner;
}

//
//  ResourceCache::MemoryHasBeenFreed					- not described in the book
//
//     This is called whenever the memory associated with a resource is actually freed
//
void ResourceCache::MemoryHasBeenFreed(unsigned int size)
{
	_allocated -= size;
}

//
// ResourceCache::Match									- not described in the book
//
//   Searches the resource cache assets for files matching the pattern. Useful for providing a 
//   a list of levels for a main menu screen, for example.
//
std::vector<std::string> ResourceCache::Match(const std::string pattern)
{
	std::vector<std::string> matchingNames;
	
	if (_pResourceFile==NULL)
	{
		return matchingNames;
	}

	int numFiles = _pResourceFile->GetNumResources();

	for (int i = 0; i < numFiles; ++i)
	{
		std::string name = _pResourceFile->GetResourceName(i);

		std::transform(name.begin(), name.end(), name.begin(), (int(*)(int)) std::tolower);

		if (WildcardMatch(pattern.c_str(), name.c_str()))
		{
			matchingNames.push_back(name);
		}
	}

	return matchingNames;
}

//
// ResourceCache::Preload								- Chapter 8, page 236
//
int ResourceCache::Preload(const std::string pattern, void (*progressCallback)(int, bool &))
{
	if (_pResourceFile==NULL)
	{
		return 0;
	}

	int numFiles = _pResourceFile->GetNumResources();
	int loaded = 0;
	bool cancel = false;

	for (int i = 0; i < numFiles; ++i)
	{
		Resource resource(_pResourceFile->GetResourceName(i));

		if (WildcardMatch(pattern.c_str(), resource.Name.c_str()))
		{
			shared_ptr<ResourceHandle> handle = globalApplicationPointer->pResourceCache->GetHandle(&resource);

			++loaded;
		}

		if (progressCallback != NULL)
		{
			progressCallback(i * 100/numFiles, cancel);
		}
	}

	return loaded;
}